<template>
  <AppLayout>
    <!-- 背景装饰 -->
    <div class="bg-decoration">
      <div class="bg-circle bg-circle-1"></div>
      <div class="bg-circle bg-circle-2"></div>
      <div class="bg-circle bg-circle-3"></div>
      <div class="bg-grid"></div>
      <div class="bg-stars"></div>
    </div>

    <div class="chat-container">
      <!-- 聊天头部 -->
      <div class="chat-header">
        <div class="header-content">
          <div class="avatar-container">
            <t-avatar size="60px" :image="roleAvatarUrl" class="avatar" />
            <div class="avatar-glow"></div>
            <div class="status-indicator" :class="{ 'online': header.status === 1 }"></div>
          </div>
          <div class="header-info">
            <h2 class="profile-name">{{ header.roleName || '角色对话' }}</h2>
            <div class="profile-meta">
              <t-tag v-if="header.status !== undefined" size="small" :theme="header.status === 1 ? 'success' : 'danger'" variant="light-outline">
                {{ header.status === 1 ? '活跃' : '已结束' }}
              </t-tag>
              <span class="dot" v-if="header.lastMessageTime">·</span>
              <span v-if="header.lastMessageTime">最近：{{ formatDate(header.lastMessageTime) }}</span>
            </div>
          </div>
          <div class="header-actions">
            <button class="action-btn follow-btn">
              <HeartIcon size="18" />
              <span>关注</span>
            </button>
          </div>
        </div>
      </div>

      <div class="chat-body">
        <!-- 消息区域 -->
        <div class="chat-messages" ref="messagesContainer">
          <!-- 加载状态 -->
          <div v-if="loadingHistory" class="loading-container">
            <div class="loading-spinner">
              <div class="spinner-ring"></div>
              <div class="spinner-ring"></div>
              <div class="spinner-ring"></div>
            </div>
            <div class="loading-text">加载聊天记录中...</div>
          </div>

          <!-- 消息列表 -->
          <transition-group name="message" tag="div" class="messages-list">
            <div
              v-for="(msg, index) in displayMessages"
              :key="msg.id"
              class="message-wrapper"
              :class="{ 'sent': msg.position === 'right', 'received': msg.position === 'left' }"
              :style="{ '--delay': `${index * 0.05}s` }"
            >
              <div class="message-container">
                <t-avatar 
                  v-if="msg.position === 'left'" 
                  size="40px" 
                  :image="roleAvatarUrl" 
                  class="message-avatar"
                >
                  {{ getAvatarFallback() }}
                </t-avatar>
                <t-avatar 
                  v-else 
                  size="40px" 
                  :image="userStore.userAvatarUrl" 
                  class="message-avatar"
                >
                  {{ userStore.avatarFallback }}
                </t-avatar>
                <div class="message-content">
                  <div class="message-bubble" :class="{ 'text': msg.type === 'text', 'audio': msg.type === 'audio', 'thinking': msg.isThinking }">
                    <!-- AI思考状态 -->
                    <div v-if="msg.isThinking" class="thinking-indicator">
                      <div class="thinking-icon">🤔</div>
                      <div class="thinking-text">AI正在思考...</div>
                      <div class="thinking-dots">
                        <div class="thinking-dot"></div>
                        <div class="thinking-dot"></div>
                        <div class="thinking-dot"></div>
                      </div>
                    </div>
                    <!-- 正常文本内容 -->
                    <div v-else-if="msg.type === 'text'" class="text-content" v-html="renderAiMarkdown(msg.content || '')"></div>
                    <!-- 音频内容 -->
                    <div v-else class="audio-content">
                      <audio :src="msg.audioUrl" controls class="audio-player"></audio>
                      <div class="audio-duration" v-if="msg.audioDuration">{{ msg.audioDuration }}s</div>
                    </div>
                  </div>
                  <div class="message-time">{{ formatTime(msg.time) }}</div>
                </div>
              </div>
            </div>
          </transition-group>
        </div>

        <!-- 输入区域 -->
        <div class="chat-footer">
          <div class="input-container">
            <t-chat-sender
              v-model="query"
              :textarea-props="{ placeholder: isAiThinking ? 'AI正在思考中，请稍候...' : '请输入消息...', autosize: { minRows: 1, maxRows: 3 }, size: 'medium', disabled: isAiThinking }"
              :loading="loading"
              :disabled="isAiThinking"
              @send="inputEnter"
              @file-select="fileSelect"
              @stop="onStop"
              class="chat-input"
            >
              <template #suffix="{ renderPresets }">
                <!-- 语音对话按钮 -->
                <transition name="button-scale">
                  <button
                    type="button"
                    class="input-action-btn voice-btn"
                    @click="openVoiceMode"
                    aria-label="进入语音对话模式"
                  >
                    <sound-icon :stroke-width="2.5" />
                  </button>
                </transition>
                
                <!-- 录音开关按钮 -->
                <transition name="button-scale">
                  <button
                    v-if="!isRecording"
                    type="button"
                    class="input-action-btn record-btn"
                    @click="startRecording"
                    aria-label="开始录音"
                  >
                    <microphone-1-filled-icon :fill-color="['currentColor','transparent']" :stroke-color="['currentColor','#0052d9']" :stroke-width="2.5" />
                  </button>
                  <button
                    v-else
                    type="button"
                    class="input-action-btn record-btn recording"
                    @click="stopRecording"
                    aria-label="停止录音"
                  >
                    <stop-circle-filled-icon :fill-color="['currentColor','transparent']" :stroke-color="['currentColor','currentColor']" :stroke-width="2.5" />
                  </button>
                </transition>

                <!-- 联网开关按钮 -->
                <transition name="button-scale">
                  <button
                    type="button"
                    class="input-action-btn web-search-btn"
                    :class="{ 'active': enableWebSearch }"
                    @click="toggleWebSearch"
                    :aria-label="enableWebSearch ? '关闭联网搜索' : '开启联网搜索'"
                    :title="enableWebSearch ? '关闭联网搜索' : '开启联网搜索'"
                  >
                    <span class="web-search-icon">🌐</span>
                    <div class="button-glow" v-if="enableWebSearch"></div>
                  </button>
                </transition>
              
              </template>
            </t-chat-sender>
          </div>
        </div>

        <!-- 语音对话全屏浮层 -->
        <transition name="voice-overlay" appear>
          <div v-if="voiceModeActive" class="voice-overlay">
            <!-- 粒子背景 -->
            <div ref="particlesContainer" class="particles-container"></div>
            
            <!-- 主要内容 -->
            <div class="voice-panel">
              <div class="voice-circle-container">
                <div class="voice-circle" :class="{ 'voice-animating': isVoiceAnimating }">
                  <div class="voice-inner-circle"></div>
                </div>
                <!-- 波纹效果 -->
                <div v-if="isVoiceAnimating" class="voice-ripple voice-ripple-1"></div>
                <div v-if="isVoiceAnimating" class="voice-ripple voice-ripple-2"></div>
                <div v-if="isVoiceAnimating" class="voice-ripple voice-ripple-3"></div>
              </div>
              
              <transition name="status-fade" mode="out-in">
                <div class="voice-status">
                  <span v-if="isRecording" class="recording-hint">
                    🎙️ 正在录音中... 
                    <br>
                    <strong style="color: #ff6b6b;">说完话请点击麦克风停止录音</strong>
                  </span>
                  <span v-else-if="isPlayingAudio">AI 正在回复...</span>
                  <span v-else-if="countdownSeconds > 0">连续模式：{{ countdownSeconds }}秒后自动开启录音</span>
                  <span v-else>连续语音对话模式 - 点击麦克风开始</span>
                </div>
              </transition>
              
              <!-- 应急恢复按钮 -->
              <transition name="fade">
                <div class="voice-debug" v-if="voiceModeActive">
                  <button class="debug-btn" @click="forceRestart">
                    状态异常？点击恢复
                  </button>
                </div>
              </transition>
              
              <div class="voice-actions">
                <button 
                  type="button" 
                  class="voice-action-btn primary" 
                  :class="{ 'recording-pulse': isRecording }"
                  @click="toggleVoiceRecording" 
                  :aria-label="isRecording ? '停止录音' : '开始录音'"
                >
                  <microphone-1-filled-icon :fill-color="['currentColor','transparent']" :stroke-color="['currentColor','currentColor']" :stroke-width="2.5" />
                </button>
                <button type="button" class="voice-action-btn danger" @click="closeVoiceMode" aria-label="关闭">
                  ✕
                </button>
              </div>
              
              <!-- 录音提示 -->
              <transition name="instruction-slide">
                <div v-if="isRecording" class="recording-instruction">
                  <div class="instruction-content">
                    💬 请开始说话，说完后点击上方麦克风停止录音
                  </div>
                </div>
              </transition>
            </div>
          </div>
        </transition>
      </div>
    </div>

    <!-- 浮动粒子效果 -->
    <div class="floating-particles" ref="floatingParticles"></div>
  </AppLayout>
</template>

<script setup lang="ts">
import { computed, ref, onMounted, onBeforeUnmount, nextTick, watch } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { useUserStore } from '@/stores/user';
import AppLayout from '@/layouts/AppLayout.vue';
import apiClient from '@/utils/api';
import { renderAiMarkdown } from '@/utils/renderMarkdown';
import { Microphone1FilledIcon, StopCircleFilledIcon, SoundIcon, HeartIcon } from 'tdesign-icons-vue-next';

// 类型定义（与后端VO对齐简化）
interface AiMessageVO { 
  id: string; 
  created: string; 
  conversationId: string; 
  senderType: 1|2; 
  messageType: 1|2; 
  content?: string; 
  audioUrl?: string; 
  audioDuration?: number; 
  messageIndex: number;
  isThinking?: boolean; // 新增：标记是否在思考状态
}
interface AiConversationVO { id: string; roleId?: string; roleName?: string; title?: string; status?: number; lastMessageTime?: string; messages: AiMessageVO[]; }
interface ConversationResponseVO { conversationId: string; content?: string; audioUrl?: string; audioDuration?: number; messageIndex?: number; }
interface AiRoleVO { id: string; avatarUrl?: string; roleName?: string; roleDesc?: string; systemPrompt?: string }

// 简单调试日志
const dbg = (...args: any[]) => { try { console.log('[chat]', ...args); } catch {} };

// 页面引用
const messagesContainer = ref<HTMLElement>();
const particlesContainer = ref<HTMLElement>();
const floatingParticles = ref<HTMLElement>();

const route = useRoute();
const router = useRouter();
const userStore = useUserStore();
const initialConvId = (route.params.id as string) || `conv-${Date.now()}`;
const nowISO = () => new Date().toISOString();
const API_BASE = (apiClient as any)?.defaults?.baseURL || '';

// 从路由中取 roleId（用于无会话时自动创建）
const routeRoleId = (route.query.roleId as string) || '';

// 新增：联网开关状态
const enableWebSearch = ref<boolean>(false);

// 新增：切换联网状态
const toggleWebSearch = () => {
  enableWebSearch.value = !enableWebSearch.value;
  dbg('联网搜索状态切换为:', enableWebSearch.value);
};

// 数据源：从后端API获取
const conversation = ref<AiConversationVO | null>(null);
const roleAvatarUrl = ref<string>('');
const roleBio = ref<string>('');
const loadingHistory = ref<boolean>(false);

// 发送输入/加载
const query = ref<string>('');
const loading = ref<boolean>(false);
const sseActive = ref<boolean>(false);
const currentSseMessageId = ref<string>('');
const sseAbortController = ref<AbortController | null>(null);
let sseBuffer = '';

// 获取角色头像回退字符
const getAvatarFallback = () => {
  const roleName = conversation.value?.roleName || '角色';
  return roleName.charAt(0).toUpperCase();
};

// 监听路由变化，重新加载对话数据
watch(() => route.params.id, async (newId) => {
  if (newId && newId !== initialConvId) {
    await fetchHistory(newId as string);
  }
}, { immediate: false });

// 录音相关状态（PCM 16k 单声道流）
const isRecording = ref<boolean>(false);
const micStream = ref<MediaStream | null>(null);
const audioContextRef = ref<AudioContext | null>(null);
const sourceNode = ref<MediaStreamAudioSourceNode | null>(null);
const processorNode = ref<ScriptProcessorNode | null>(null);

// 语音模式 UI - 连续对话模式
const voiceModeActive = ref<boolean>(false);
const isVoiceAnimating = ref<boolean>(false);
const autoRestartTimer = ref<number | null>(null);
const autoRestartDelay = 5000;
const countdownSeconds = ref<number>(0);

const openVoiceMode = () => { 
  voiceModeActive.value = true; 
  
  // 重置所有状态，确保干净的开始
  isVoiceAnimating.value = false;
  isPlayingAudio.value = false;
  countdownSeconds.value = 0;
  if (autoRestartTimer.value) {
    clearTimeout(autoRestartTimer.value);
    autoRestartTimer.value = null;
  }
  
  dbg('进入语音模式，重置状态完成');
  
  // 进入语音模式后自动开始第一轮录音
  setTimeout(() => {
    if (voiceModeActive.value && !isRecording.value) {
      dbg('准备开始第一轮录音');
      startRecording();
    }
  }, 500);
};

const closeVoiceMode = () => {
  // 清理自动重启计时器
  if (autoRestartTimer.value) {
    clearTimeout(autoRestartTimer.value);
    autoRestartTimer.value = null;
  }
  countdownSeconds.value = 0;
  
  if (isRecording.value) { try { stopRecording(); } catch {} }
  voiceModeActive.value = false;
  isVoiceAnimating.value = false;
};

const toggleVoiceRecording = () => {
  if (isRecording.value) {
    stopRecording();
  } else {
    if (autoRestartTimer.value) {
      clearTimeout(autoRestartTimer.value);
      autoRestartTimer.value = null;
      countdownSeconds.value = 0;
    }
    startRecording();
  }
};

// 应急恢复函数
const forceRestart = async () => {
  dbg('执行应急恢复，当前状态:', {
    isRecording: isRecording.value,
    isPlayingAudio: isPlayingAudio.value,
    isVoiceAnimating: isVoiceAnimating.value,
    voiceWsReady: voiceWsReady.value,
    autoRestartTimer: !!autoRestartTimer.value
  });
  
  if (isRecording.value) {
    try { stopRecording(); } catch {}
  }
  
  if (autoRestartTimer.value) {
    clearTimeout(autoRestartTimer.value);
    autoRestartTimer.value = null;
  }
  
  isRecording.value = false;
  isPlayingAudio.value = false;
  isVoiceAnimating.value = false;
  countdownSeconds.value = 0;
  
  audioQueue.value.forEach(url => {
    try { URL.revokeObjectURL(url); } catch {}
  });
  audioQueue.value = [];
  
  if (voiceWs.value) {
    try { voiceWs.value.close(); } catch {}
    voiceWs.value = null;
  }
  
  dbg('应急恢复：状态已重置，准备重新开始录音');
  
  setTimeout(() => {
    if (voiceModeActive.value) {
      startRecording();
    }
  }, 1000);
};

// WebSocket 语音流
const voiceWs = ref<WebSocket | null>(null);
const voiceWsReady = ref<boolean>(false);

const initVoiceWs = async (): Promise<void> => {
  if (voiceWs.value && voiceWs.value.readyState === WebSocket.OPEN) return;
  const convId = conversation.value?.id || initialConvId;
  const initRes: any = await apiClient.post('/ai/conversation/message/voice/init', null, { params: { conversationId: convId } });
  const wsUrl: string = initRes.data.result.websocketUrl || "";
  if (!wsUrl) throw new Error('未获取到 websocketUrl');
  return new Promise<void>((resolve, reject) => {
    try {
      const ws = new WebSocket(wsUrl);
      voiceWs.value = ws;
      voiceWsReady.value = false;
      ws.onopen = () => { voiceWsReady.value = true; dbg('ws open', wsUrl); resolve(); };
      ws.onclose = (e) => { voiceWsReady.value = false; dbg('ws close', e?.code, e?.reason); };
      ws.onerror = (e) => { voiceWsReady.value = false; dbg('ws error', e); reject(new Error('WebSocket 连接失败')); };
      ws.onmessage = async (evt) => {
        try {
          const data = typeof evt.data === 'string' ? evt.data : '';
          if (!data) return;
          dbg('ws msg raw', data.slice(0, 200));
          let payload: any; try { payload = JSON.parse(data); } catch { payload = {}; }
          dbg('ws msg json', payload);
          const t = payload?.type || '';

          if (t === 'asr_result') {
            const asrText = payload.text || '';
            const isFinal = payload.isFinal || false;
            dbg(`ASR 识别结果: ${asrText} (最终结果: ${isFinal})`);
            
            if (isFinal && asrText.trim()) {
              const convIdUse = conversation.value?.id || initialConvId;
              if (!conversation.value) {
                conversation.value = { id: convIdUse, roleId: '', roleName: '', title: '', messages: [] } as any;
              }
              
              const userMessage = {
                id: `msg-voice-${Date.now()}`,
                created: nowISO(),
                conversationId: convIdUse,
                senderType: 1,
                messageType: 1,
                content: asrText.trim(),
                messageIndex: (conversation.value?.messages.length || 0) + 1,
                isThinking: false
              } as AiMessageVO;
              
              conversation.value!.messages.push(userMessage);
              scrollToBottom();
              dbg('用户语音消息已添加到聊天记录:', asrText);
            }
          }

          if (t === 'ai_text_chunk' || t === 'llm_delta' || t === 'llm_final') {
            const convIdUse = conversation.value?.id || convId;
            const msgId = payload?.messageId || currentSseMessageId.value || (currentSseMessageId.value = `ws-${Date.now()}`);
            const chunk = payload?.chunk || payload?.text || payload?.content || '';
            if (!chunk) return;
            const block = `event: text_chunk\ndata: ${JSON.stringify({ conversationId: convIdUse, messageId: msgId, chunk })}\n\n`;
            await handleSseEventBlock(block, conversation.value?.roleId || '');
          }

          if (t === 'ai_audio_chunk' && payload.audioData) {
            isVoiceAnimating.value = true;
            playAudioChunk(payload.audioData);
          }

          if (t === 'ai_response_complete') {
            dbg('AI 回复完成');
            currentSseMessageId.value = '';
            
            setTimeout(() => {
              if (voiceModeActive.value && !isRecording.value) {
                dbg('准备启动自动重启倒计时（延迟更长以避免ASR冲突）');
                startAutoRestartCountdown();
              } else {
                dbg('跳过自动重启，当前状态:', {
                  voiceModeActive: voiceModeActive.value,
                  isRecording: isRecording.value
                });
              }
            }, 3000);
          }

          if (t === 'error') {
            dbg('WebSocket 错误:', payload.error);
            alert('语音对话错误: ' + payload.error);
          }

        } catch (e) {
          dbg('WebSocket 消息解析/处理错误', e);
        }
      };
    } catch (e) {
      reject(e as any);
    }
  });
};

const sendWsControl = (action: string) => {
  if (!voiceWs.value || voiceWs.value.readyState !== WebSocket.OPEN) return;
  try { voiceWs.value.send(JSON.stringify({ action })); } catch {}
};

// 音频播放队列
const audioQueue = ref<string[]>([]);
const isPlayingAudio = ref<boolean>(false);
const audioPermissionGranted = ref<boolean>(false);

const prepareAudioPermission = async (): Promise<void> => {
  if (audioPermissionGranted.value) return;
  
  try {
    const silentAudio = new Audio();
    silentAudio.volume = 0.01;
    silentAudio.src = "data:audio/wav;base64,UklGRigAAABXQVZFZm10IBAAAAAABAAARKwAAIhYAQACABAAZGF0YQQAAAAAAA==";
    await silentAudio.play();
    audioPermissionGranted.value = true;
    dbg('音频播放权限获取成功');
  } catch (e) {
    dbg('音频播放权限获取失败:', e);
  }
};

const playAudioChunk = (base64Audio: string) => {
  try {
    const audioData = atob(base64Audio);
    const arrayBuffer = new ArrayBuffer(audioData.length);
    const view = new Uint8Array(arrayBuffer);
    for (let i = 0; i < audioData.length; i++) {
      view[i] = audioData.charCodeAt(i);
    }

    const audioBlob = new Blob([arrayBuffer], { type: "audio/wav" });
    const audioUrl = URL.createObjectURL(audioBlob);

    audioQueue.value.push(audioUrl);

    if (!isPlayingAudio.value) {
      playNextAudio();
    }

  } catch (error: any) {
    dbg(`播放音频失败: ${error.message}`);
  }
};

const startAutoRestartCountdown = () => {
  if (autoRestartTimer.value) {
    clearTimeout(autoRestartTimer.value);
  }
  
  dbg('开始自动重启倒计时，当前状态:', {
    voiceModeActive: voiceModeActive.value,
    isRecording: isRecording.value,
    isPlayingAudio: isPlayingAudio.value
  });
  
  countdownSeconds.value = Math.ceil(autoRestartDelay / 1000);
  
  const countdownInterval = setInterval(() => {
    countdownSeconds.value--;
    if (countdownSeconds.value <= 0) {
      clearInterval(countdownInterval);
    }
  }, 1000);
  
  autoRestartTimer.value = window.setTimeout(() => {
    dbg('自动重启计时器触发，检查状态:', {
      voiceModeActive: voiceModeActive.value,
      isRecording: isRecording.value,
      isPlayingAudio: isPlayingAudio.value
    });
    
    if (voiceModeActive.value && !isRecording.value && !isPlayingAudio.value) {
      dbg('连续模式：自动重启录音');
      startRecording();
    } else {
      dbg('跳过自动重启，条件不满足');
    }
    autoRestartTimer.value = null;
    countdownSeconds.value = 0;
  }, autoRestartDelay);
};

const playNextAudio = () => {
  if (audioQueue.value.length === 0) {
    isPlayingAudio.value = false;
    return;
  }

  isPlayingAudio.value = true;
  const audioUrl = audioQueue.value.shift();
  if (!audioUrl) {
    playNextAudio();
    return;
  }
  const audio = new Audio(audioUrl);

  audio.play()
    .then(() => {
      dbg(`成功播放音频`);
    })
    .catch(e => dbg(`播放音频失败: ${e.message}`));

  audio.onended = () => {
    URL.revokeObjectURL(audioUrl);
    playNextAudio();
  };

  audio.onerror = () => {
    URL.revokeObjectURL(audioUrl);
    playNextAudio();
  };
};

// 头部信息派生
const header = computed(() => ({
  id: conversation.value?.id || null,
  roleName: conversation.value?.roleName,
  title: conversation.value?.title,
  status: conversation.value?.status,
  lastMessageTime: conversation.value?.lastMessageTime
}));

// TDesign Chat 消息结构
const displayMessages = computed(() => {
  const list = (conversation.value?.messages || []);
  return list.map(m => ({
    id: m.id,
    position: m.senderType === 1 ? 'right' : 'left',
    type: m.messageType === 2 ? 'audio' : 'text',
    content: m.isThinking ? 'AI正在思考...' : (m.content || (m.senderType === 2 ? '暂无内容' : '[无内容]')),
    audioUrl: m.audioUrl,
    audioDuration: m.audioDuration,
    time: m.created,
    isThinking: m.isThinking || false
  }));
});

// 判断是否有AI正在思考，用于禁用输入框
const isAiThinking = computed(() => {
  const messages = conversation.value?.messages || [];
  return messages.some(m => m.senderType === 2 && m.isThinking);
});

// 时间格式化
const formatTime = (iso: string): string => new Date(iso).toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' });
const formatDate = (iso?: string): string => {
  if (!iso) return '';
  const d = new Date(iso);
  if (isNaN(d.getTime())) return String(iso);
  return `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')} ${String(d.getHours()).padStart(2,'0')}:${String(d.getMinutes()).padStart(2,'0')}`;
};

// 滚动到底部
const scrollToBottom = () => {
  nextTick(() => {
    if (messagesContainer.value) {
      messagesContainer.value.scrollTop = messagesContainer.value.scrollHeight;
    }
  });
};

// 从对话列表中获取特定对话的信息
const fetchConversationInfo = async (id: string) => {
  try {
    const response = await apiClient.get('/ai/conversation/list', {
      params: {
        page: 1,
        size: 100, // 获取足够多的对话以确保能找到目标对话
        orderBy: 'lastMessageTime',
        orderDirection: 'desc'
      }
    });

    if (response.data?.success && Array.isArray(response.data.result)) {
      const conversations = response.data.result;
      const targetConv = conversations.find((conv: any) => conv.id === id);
      
      if (targetConv) {
        dbg('找到对话信息:', targetConv);
        return {
          id: targetConv.id,
          roleId: targetConv.roleId,
          roleName: targetConv.roleName,
          title: targetConv.title,
          status: targetConv.status,
          lastMessageTime: targetConv.lastMessageTime
        };
      }
    }
  } catch (error) {
    dbg('获取对话信息失败:', error);
  }
  return null;
};

// 拉取会话历史
const fetchHistory = async (id: string) => {
  dbg('fetchHistory -> id:', id);
  loadingHistory.value = true;
  try {
    // 使用正确的后端接口路径
    const res = await apiClient.get(`/ai/conversation/messages/${id}`) as any;
    dbg('fetchHistory res:', res);
    
    if (res && res.data?.success && Array.isArray(res.data.result)) {
      // 直接获取消息列表
      const messages = res.data.result as AiMessageVO[];
      
      // 构建对话对象
      if (!conversation.value) {
        conversation.value = { 
          id, 
          roleId: '', 
          roleName: '', 
          title: '', 
          messages: [] 
        } as any;
      }
      
      conversation.value.messages = messages.map(msg => ({
        ...msg,
        isThinking: false // 历史消息都不是思考状态
      }));
      dbg('history applied, messages:', messages?.length);
      
      // 从对话列表中获取角色信息
      const convInfo = await fetchConversationInfo(id);
      if (convInfo) {
        conversation.value.roleId = convInfo.roleId;
        conversation.value.roleName = convInfo.roleName;
        conversation.value.title = convInfo.title;
        conversation.value.status = convInfo.status;
        conversation.value.lastMessageTime = convInfo.lastMessageTime;
        
        // 获取角色详情
        if (convInfo.roleId) {
          await fetchRoleDetail(convInfo.roleId);
        }
      }
    } else {
      conversation.value = { 
        id, 
        roleId: conversation.value?.roleId, 
        roleName: conversation.value?.roleName, 
        title: conversation.value?.title || '', 
        messages: [] 
      } as any;
      dbg('history fallback -> empty');
    }
  } catch (error) {
    dbg('fetchHistory error:', error);
    conversation.value = { 
      id, 
      roleId: conversation.value?.roleId, 
      roleName: conversation.value?.roleName, 
      title: conversation.value?.title || '', 
      messages: [] 
    } as any;
    dbg('history error -> empty');
  } finally {
    loadingHistory.value = false;
    scrollToBottom();
  }
};


// 在Chat.vue中添加专门的事件处理器
const handleSseEventBlock = async (block: string, fallbackRoleId: string, aiMsgId?: string) => {
  const lines = block.split('\n');
  let ev = '';
  let data = '';
  for (const line of lines) {
    if (line.startsWith('event:')) {
      ev = line.slice(6).trim();
    } else if (line.startsWith('data:')) {
      data += line.slice(5).trim();
    }
  }
  if (!ev || !data) return;
  
  try {
    const payload = JSON.parse(data);
    
    // 处理不同的SSE事件类型
    switch (ev) {
      case 'conversation_created':
        handleConversationCreated(payload, fallbackRoleId);
        break;
        
      case 'ai_message_start':
        handleAiMessageStart(payload);
        break;
        
      case 'ai_thinking_end':
        handleAiThinkingEnd(payload);
        break;
        
      case 'text_chunk':
        handleTextChunk(payload, fallbackRoleId, aiMsgId);
        break;
        
      case 'message_complete':
      case 'stream_end':
      case 'complete':
        handleStreamComplete(payload);
        break;
        
      case 'error':
        handleStreamError(payload);
        break;
        
      default:
        dbg('未知SSE事件类型:', ev);
    }
  } catch (e) {
    dbg('SSE parse error:', e, block);
  }
};

// 处理对话创建事件
const handleConversationCreated = (payload: any, fallbackRoleId: string) => {
  const convId = payload.conversationId as string;
  if (convId) {
    if (!conversation.value) {
      conversation.value = { 
        id: convId, 
        roleId: fallbackRoleId, 
        roleName: '', 
        title: payload.title || '', 
        messages: [] 
      } as any;
    } else {
      conversation.value.id = convId;
    }
    
    // 立即更新URL，确保用户看到正确的对话ID
    if (route.params.id !== convId) {
      router.replace({ name: 'Chat', params: { id: convId } }).catch(err => {
        dbg('路由跳转失败:', err);
      });
    }
    
    if (fallbackRoleId) fetchRoleDetail(fallbackRoleId);
  }
};

// 处理AI消息开始事件（创建思考状态）
const handleAiMessageStart = (payload: any) => {
  const convId = payload.conversationId as string;
  const msgId = payload.messageId as string;
  
  if (!conversation.value) {
    conversation.value = { 
      id: convId, 
      roleId: '', 
      roleName: '', 
      title: '', 
      messages: [] 
    } as any;
  }
  
  const list = conversation.value!.messages;
  const existingMsg = list.find(m => m.id === msgId);
  
  // 如果消息已存在，直接使用现有消息，不创建新的
  if (existingMsg) {
    if (!existingMsg.isThinking) {
      existingMsg.isThinking = true;
      dbg('重新设置现有消息的思考状态:', msgId);
    }
    currentSseMessageId.value = msgId;
    return;
  }
  
  // 检查是否已有相同ID的消息正在处理
  if (currentSseMessageId.value === msgId) {
    dbg('消息已在处理中，跳过创建:', msgId);
    return;
  }
  
  // 先清除其他可能的思考状态，防止重复
  list.forEach(msg => {
    if (msg.senderType === 2 && msg.isThinking) {
      msg.isThinking = false;
      dbg('清除旧的思考状态:', msg.id);
    }
  });
  
  const newMsg: AiMessageVO = { 
    id: msgId, 
    created: nowISO(), 
    conversationId: convId, 
    senderType: 2 as 2, 
    messageType: 1 as 1, 
    content: 'AI正在思考...', 
    messageIndex: (list.length + 1),
    isThinking: true
  };
  list.push(newMsg);
  scrollToBottom();
  dbg('创建AI思考消息:', msgId);
  
  currentSseMessageId.value = msgId;
};

// 处理AI思考结束事件
const handleAiThinkingEnd = (payload: any) => {
  const msgId = payload.messageId as string;
  const list = conversation.value?.messages || [];
  const targetMsg = list.find(m => m.id === msgId);
  
  if (targetMsg && targetMsg.isThinking) {
    targetMsg.isThinking = false;
    dbg('AI思考结束，开始接收文本流:', msgId);
    scrollToBottom();
  }
};

// 处理文本块事件
const handleTextChunk = (payload: any, fallbackRoleId: string, aiMsgId?: string) => {
  const convId = payload.conversationId as string;
  const msgId = payload.messageId as string || aiMsgId;
  let chunk = String(payload.chunk ?? '');
  chunk = chunk.replace(/\r/g, '');
  
  if (!convId || !msgId) return;
  if (!conversation.value) {
    conversation.value = { 
      id: convId, 
      roleId: fallbackRoleId, 
      roleName: '', 
      title: '', 
      messages: [] 
    } as any;
  }
  
  const list = conversation.value!.messages;
  let targetMsg = list.find(m => m.id === msgId);
  
  if (!targetMsg) {
    // 如果没有找到消息，可能是因为ai_message_start事件丢失
    const newMsg: AiMessageVO = { 
      id: msgId, 
      created: nowISO(), 
      conversationId: convId, 
      senderType: 2 as 2, 
      messageType: 1 as 1, 
      content: chunk, // 直接设置为接收到的文本块
      messageIndex: (list.length + 1),
      isThinking: false // 已经开始接收文本，不需要思考状态
    };
    list.push(newMsg);
    targetMsg = newMsg;
    scrollToBottom();
    dbg('补偿创建AI消息:', msgId);
  } else {
    // 立即清除思考状态 - 这是关键修复
    if (targetMsg.isThinking) {
      targetMsg.isThinking = false;
      // 清除思考时的占位内容，直接设置为当前文本块
      targetMsg.content = chunk;
      dbg('收到文本块，立即清除思考状态并设置内容:', msgId);
    } else {
      // 更新消息内容
      const prev = targetMsg.content || '';
      if (chunk && prev && chunk.startsWith(prev)) {
        targetMsg.content = chunk;
      } else {
        targetMsg.content = prev + chunk;
      }
    }
  }
  
  // 同时清除所有其他可能的思考状态消息，防止重复
  list.forEach(msg => {
    if (msg.id !== msgId && msg.senderType === 2 && msg.isThinking) {
      msg.isThinking = false;
      dbg('清除其他思考状态消息:', msg.id);
    }
  });
  
  sseActive.value = true;
  scrollToBottom();
};

// 处理流完成事件
const handleStreamComplete = (payload: any) => {
  const list = conversation.value?.messages || [];
  
  // 确保清除所有思考状态
  list.forEach(msg => {
    if (msg.senderType === 2 && msg.isThinking) {
      msg.isThinking = false;
      dbg('在stream_complete中清除思考状态:', msg.id);
    }
  });
  
  sseActive.value = false;
  loading.value = false;
  dbg('SSE流结束，清除所有思考状态');
};

// 处理流错误事件
const handleStreamError = (payload: any) => {
  const list = conversation.value?.messages || [];
  const errorMsg = list.find(m => m.id === currentSseMessageId.value);
  
  if (errorMsg && errorMsg.isThinking) {
    errorMsg.isThinking = false;
    errorMsg.content = payload.message || '发生错误';
    dbg('在error事件中清除思考状态');
  }
  
  sseActive.value = false;
  loading.value = false;
};

// 无会话时，根据 roleId 通过 SSE 自动开局
const startFromRole = async (roleId: string) => {
  dbg('startFromRole SSE roleId:', roleId);
  if (sseAbortController.value) {
    try { sseAbortController.value.abort(); } catch {}
    sseAbortController.value = null;
  }
  
  // 立即创建对话和AI思考状态的消息占位符
  if (!conversation.value) {
    conversation.value = { 
      id: initialConvId, 
      roleId: roleId, 
      roleName: '', 
      title: '', 
      messages: [] 
    } as any;
  }
  
  // 创建AI思考状态的消息占位符
  const aiMsgId = `ai-thinking-${Date.now()}`;
  const thinkingMsg: AiMessageVO = {
    id: aiMsgId,
    created: nowISO(),
    conversationId: conversation.value.id,
    senderType: 2 as 2,
    messageType: 1 as 1,
    content: '',
    messageIndex: 1,
    isThinking: true
  };
  conversation.value.messages.push(thinkingMsg);
  currentSseMessageId.value = aiMsgId;
  scrollToBottom();
  dbg('创建初始AI思考状态消息');
  
  const controller = new AbortController();
  sseAbortController.value = controller;
  sseBuffer = '';
  loading.value = true; // 设置加载状态
  try {
    const resp = await fetch(`${API_BASE}/ai/conversation/start`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Accept': 'text/event-stream',
      },
      body: JSON.stringify({ 
        roleId, 
        title: '',
        enableWebSearch: enableWebSearch.value // 传递联网搜索状态
      }),
      signal: controller.signal,
    });
    if (!resp.ok || !resp.body) {
      throw new Error('SSE start request failed');
    }
    const reader = resp.body.getReader();
    const decoder = new TextDecoder('utf-8');
    let finalConversationId: string | null = null;
    while (true) {
      const { value, done } = await reader.read();
      if (done) break;
      const chunkText = decoder.decode(value, { stream: true });
      sseBuffer += chunkText;
      let sepIndex;
      while ((sepIndex = sseBuffer.indexOf('\n\n')) !== -1) {
        const block = sseBuffer.slice(0, sepIndex).trim();
        sseBuffer = sseBuffer.slice(sepIndex + 2);
        if (block) await handleSseEventBlock(block, roleId);
        if (conversation.value?.id) finalConversationId = conversation.value.id;
      }
    }
    const convId = finalConversationId || conversation.value?.id;
    if (convId) {
      try { router.replace({ name: 'Chat', params: { id: convId } }); } catch {}
    }
  } catch (e) {
    dbg('SSE start error:', e);
  } finally {
    sseAbortController.value = null;
    sseActive.value = false;
    loading.value = false;
    currentSseMessageId.value = '';
  }
};

// 添加默认头像生成函数
const getDefaultAvatar = (name: string): string => {
  const colors = ['#7c4dff', '#ff6b6b', '#4ecdc4', '#45b7d1', '#96ceb4', '#feca57'];
  const colorIndex = name.length % colors.length;
  const firstChar = name.charAt(0).toUpperCase();
  
  const svg = `
    <svg width="60" height="60" xmlns="http://www.w3.org/2000/svg">
      <circle cx="30" cy="30" r="30" fill="${colors[colorIndex]}"/>
      <text x="30" y="38" text-anchor="middle" fill="white" font-size="24" font-weight="bold">${firstChar}</text>
    </svg>
  `;
  
  return `data:image/svg+xml;base64,${btoa(svg)}`;
};

// 拉取角色详情(头像/简介)
const fetchRoleDetail = async (roleId: string) => {
  try {
    const res = await apiClient.get('/ai/role/', { params: { id: roleId } }) as any;
    if (res && res.data?.success && res.data.result) {
      const r = res.data.result as AiRoleVO;
      roleAvatarUrl.value = r.avatarUrl || getDefaultAvatar(r.roleName || '角色');
      const bio = r.roleDesc || r.systemPrompt || '';
      if (bio) roleBio.value = bio;
      if (r.roleName && !conversation.value?.roleName && conversation.value) {
        conversation.value.roleName = r.roleName;
      }
    } else {
      roleAvatarUrl.value = getDefaultAvatar('角色');
    }
  } catch (e) {
    dbg('获取角色详情失败:', e);
    roleAvatarUrl.value = getDefaultAvatar('角色');
  }
};

// 清空历史
const clearHistory = async () => {
  const id = conversation.value?.id;
  if (!id) { conversation.value = { id: initialConvId, roleId: conversation.value?.roleId, roleName: conversation.value?.roleName, title: '', messages: [] } as any; return; }
  try { await apiClient.post(`/ai/conversation/end/${id}`); } catch {}
  if (conversation.value) conversation.value.messages = [];
};

// 发送文本（配合 t-chat-sender）-> 使用 SSE 流式
const sendMessage = async (text: string) => {
  dbg('sendMessage text:', text);
  const convId = conversation.value?.id || initialConvId;
  if (!conversation.value) conversation.value = { id: convId, roleId: '', roleName: '', title: '', messages: [] } as any;
  const list = (conversation.value?.messages || []);
  const nextIndex = list.length + 1;
  
  // 先本地追加用户消息
  const userMsg: AiMessageVO = { 
    id: `msg-${Date.now()}`, 
    created: nowISO(), 
    conversationId: convId, 
    senderType: 1 as 1, 
    messageType: 1 as 1, 
    content: text, 
    messageIndex: nextIndex,
    isThinking: false
  };
  list.push(userMsg);
  scrollToBottom();

  // 清除所有现有的思考状态
  list.forEach(msg => {
    if (msg.senderType === 2 && msg.isThinking) {
      msg.isThinking = false;
    }
  });

  const aiMsgId = `ai-${Date.now()}`;
  let hasReceivedFirstChunk = false;
  let isStreamCompleted = false; // 添加流完成标志

  // 只在有旧连接时才中止，避免不必要的abort
  if (sseAbortController.value && sseActive.value) { 
    try { 
      dbg('中止上一个SSE连接');
      sseAbortController.value.abort(); 
    } catch (e) {
      dbg('中止上一个连接失败:', e);
    }
    sseAbortController.value = null; 
  }
  
  const controller = new AbortController();
  sseAbortController.value = controller;
  sseBuffer = '';
  sseActive.value = true;
  loading.value = true;
  
  // 添加连接超时保护（30秒）
  const connectionTimeout = setTimeout(() => {
    if (!isStreamCompleted && controller && !controller.signal.aborted) {
      dbg('SSE连接超时，主动关闭');
      controller.abort();
    }
  }, 30000);
  
  try {
    // 修改URL构建，添加联网搜索参数
    const url = `${API_BASE}/ai/conversation/message/text?conversationId=${encodeURIComponent(convId)}&content=${encodeURIComponent(text)}&enableWebSearch=${enableWebSearch.value}`;
    
    dbg('开始SSE请求:', url);
    const resp = await fetch(url, {
      method: 'POST',
      headers: { 
        'Accept': 'text/event-stream',
        'Cache-Control': 'no-cache',
        'Connection': 'keep-alive'
      },
      signal: controller.signal,
    });
    
    if (!resp.ok) {
      throw new Error(`HTTP ${resp.status}: ${resp.statusText}`);
    }
    
    if (!resp.body) {
      throw new Error('Response body is null');
    }
    
    dbg('SSE连接建立成功，开始读取流数据');
    const reader = resp.body.getReader();
    const decoder = new TextDecoder('utf-8');
    
    try {
      while (!controller.signal.aborted && !isStreamCompleted) {
        const { value, done } = await reader.read();
        
        if (done) {
          dbg('SSE流正常结束');
          isStreamCompleted = true;
          break;
        }
        
        const chunkText = decoder.decode(value, { stream: true });
        sseBuffer += chunkText;
        
        let sepIndex;
        while ((sepIndex = sseBuffer.indexOf('\n\n')) !== -1) {
          const block = sseBuffer.slice(0, sepIndex).trim();
          sseBuffer = sseBuffer.slice(sepIndex + 2);
          
          if (block) {
            try {
              await handleSseEventBlock(block, conversation.value?.roleId || '', aiMsgId);
              
              // 收到第一个有效数据块时清除加载状态
              if (!hasReceivedFirstChunk) {
                loading.value = false;
                hasReceivedFirstChunk = true;
                dbg('收到首个SSE数据块，清除加载状态');
              }
            } catch (blockError) {
              dbg('处理SSE事件块失败:', blockError);
              // 继续处理，不中断整个流
            }
          }
        }
      }
    } finally {
      // 确保reader被正确关闭
      try {
        reader.releaseLock();
      } catch (e) {
        dbg('释放reader锁失败:', e);
      }
    }
    
  } catch (e: any) {
    dbg('SSE请求异常:', e);
    
    // 只有在非主动取消的情况下才显示错误
    if (!controller.signal.aborted) {
      const thinkingMsg = list.find(m => m.id === aiMsgId);
      if (thinkingMsg) {
        thinkingMsg.isThinking = false;
        thinkingMsg.content = `连接错误: ${e?.message || '未知错误'}`;
      } else {
        // 如果没有找到AI消息，创建一个错误消息
        const errorMsg: AiMessageVO = {
          id: aiMsgId,
          created: nowISO(),
          conversationId: convId,
          senderType: 2 as 2,
          messageType: 1 as 1,
          content: `连接错误: ${e?.message || '未知错误'}`,
          messageIndex: nextIndex + 1,
          isThinking: false
        };
        list.push(errorMsg);
      }
      scrollToBottom();
    } else {
      dbg('SSE连接被主动取消');
    }
  } finally {
    // 清理超时定时器
    clearTimeout(connectionTimeout);
    
    // 关键修复：只有在连接完成或出错时才清理，不要主动abort
    if (isStreamCompleted || controller.signal.aborted) {
      dbg('SSE连接已完成，清理资源');
      sseAbortController.value = null;
      sseActive.value = false;
      loading.value = false;
      currentSseMessageId.value = '';
    }
    
    // 不要在这里调用abort()，这是导致连接提前关闭的主要原因
  }
};

const inputEnter = async () => {
  if (loading.value) return;
  const text = query.value.trim();
  if (!text) return;
  dbg('inputEnter -> text:', text);
  const tmp = text;
  query.value = '';
  await sendMessage(tmp);
};

// 录音功能 - 开始录音 (PCM 16k 单声道流)
const startRecording = async () => {
  try {
    await prepareAudioPermission();
    
    await initVoiceWs();
    if (!voiceWs.value || voiceWs.value.readyState !== WebSocket.OPEN) {
      throw new Error('WebSocket 未连接');
    }

    micStream.value = await navigator.mediaDevices.getUserMedia({
      audio: {
        sampleRate: 16000,
        channelCount: 1,
        echoCancellation: true,
        noiseSuppression: true
      }
    });
    dbg('麦克风权限获取成功');

    audioContextRef.value = new (window.AudioContext || (window as any).webkitAudioContext)({
      sampleRate: 16000
    });

    sourceNode.value = audioContextRef.value.createMediaStreamSource(micStream.value);
    processorNode.value = audioContextRef.value.createScriptProcessor(2048, 1, 1);

    processorNode.value.onaudioprocess = (e) => {
      if (!voiceWs.value || voiceWs.value.readyState !== WebSocket.OPEN || !isRecording.value) return;

      const inputData = e.inputBuffer.getChannelData(0);
      const buffer = new ArrayBuffer(inputData.length * 2);
      const view = new DataView(buffer);

      for (let i = 0; i < inputData.length; i++) {
        const sample = Math.max(-1, Math.min(1, inputData[i]));
        const intSample = sample < 0 ? sample * 0x8000 : sample * 0x7FFF;
        view.setInt16(i * 2, intSample, true);
      }
      voiceWs.value.send(buffer);
    };

    sourceNode.value.connect(processorNode.value);
    processorNode.value.connect(audioContextRef.value.destination);

    isRecording.value = true;
    isVoiceAnimating.value = true;
    sendWsControl('start_recording');
    dbg('WebSocket 录音已开始，音频播放权限:', audioPermissionGranted.value);
  } catch (error: any) {
    dbg('Recording error:', error);
    alert('无法访问麦克风或建立语音通道失败: ' + error.message);
    isRecording.value = false;
  }
};

// 录音功能 - 停止录音
const stopRecording = async () => {
  if (!isRecording.value) return;

  isRecording.value = false;
  sendWsControl('stop_recording');

  if (!audioPermissionGranted.value) {
    try {
      await prepareAudioPermission();
    } catch (e) {
      dbg('停止录音时获取播放权限失败:', e);
    }
  }

  if (micStream.value) {
    micStream.value.getTracks().forEach(t => t.stop());
    micStream.value = null;
  }
  if (processorNode.value) {
    processorNode.value.disconnect();
    processorNode.value = null;
  }
  if (sourceNode.value) {
    sourceNode.value.disconnect();
    sourceNode.value = null;
  }
  
  if (audioContextRef.value) {
    setTimeout(() => {
      if (audioContextRef.value && !isRecording.value) {
        audioContextRef.value.close();
        audioContextRef.value = null;
        dbg('AudioContext 已延迟关闭');
      }
    }, 5000);
  }

  dbg('WebSocket 录音已停止，播放权限状态:', audioPermissionGranted.value);
};

// 兼容旧函数名（已不再使用 HTTP 上传）
const sendVoiceMessage = async (_audioBlob: Blob) => { console.warn('sendVoiceMessage 已切为 WebSocket 实时语音，无需HTTP上传'); };

// 文件选择（用于上传语音文件） - 禁用或提示
const fileSelect = (file: File) => {
  alert('文件上传已禁用，请使用实时语音对话功能。');
  dbg('File upload attempted but disabled:', file);
};

const onStop = () => { loading.value = false; };

// 点击加号的示例回调（可扩展更多功能）
const onPlusClick = () => {
  // 这里可以打开附件/功能面板，先占位
};

// 浮动粒子动画
const floatingParticlesList: Array<{
  x: number; y: number; vx: number; vy: number; opacity: number; size: number;
}> = [];
let floatingAnimationId: number;

const createFloatingParticles = () => {
  if (!floatingParticles.value) return;
  
  const container = floatingParticles.value;
  const rect = container.getBoundingClientRect();
  
  for (let i = 0; i < 15; i++) {
    floatingParticlesList.push({
      x: Math.random() * rect.width,
      y: Math.random() * rect.height,
      vx: (Math.random() - 0.5) * 0.2,
      vy: (Math.random() - 0.5) * 0.2,
      opacity: Math.random() * 0.3 + 0.1,
      size: Math.random() * 1.5 + 0.5
    });
  }
};

const animateFloatingParticles = () => {
  if (!floatingParticles.value) return;
  
  const container = floatingParticles.value;
  const rect = container.getBoundingClientRect();
  
  container.innerHTML = '';
  
  floatingParticlesList.forEach((particle) => {
    particle.x += particle.vx;
    particle.y += particle.vy;
    
    if (particle.x < 0 || particle.x > rect.width) particle.vx *= -1;
    if (particle.y < 0 || particle.y > rect.height) particle.vy *= -1;
    
    const dot = document.createElement('div');
    dot.className = 'floating-particle';
    dot.style.left = particle.x + 'px';
    dot.style.top = particle.y + 'px';
    dot.style.opacity = particle.opacity.toString();
    dot.style.width = particle.size + 'px';
    dot.style.height = particle.size + 'px';
    
    container.appendChild(dot);
  });
  
  floatingAnimationId = requestAnimationFrame(animateFloatingParticles);
};

// 语音模式粒子动画相关
const particles: Array<{
  x: number; y: number; vx: number; vy: number; opacity: number; size: number;
}> = [];
let animationId: number;

const createParticles = () => {
  if (!particlesContainer.value) return;
  
  const container = particlesContainer.value;
  const rect = container.getBoundingClientRect();
  
  for (let i = 0; i < 25; i++) {
    particles.push({
      x: Math.random() * rect.width,
      y: Math.random() * rect.height,
      vx: (Math.random() - 0.5) * 0.3,
      vy: (Math.random() - 0.5) * 0.3,
      opacity: Math.random() * 0.4 + 0.1,
      size: Math.random() * 2 + 0.5
    });
  }
};

const animateParticles = () => {
  if (!particlesContainer.value) return;
  
  const container = particlesContainer.value;
  const rect = container.getBoundingClientRect();
  
  container.innerHTML = '';
  
  particles.forEach((particle) => {
    particle.x += particle.vx;
    particle.y += particle.vy;
    
    if (particle.x < 0 || particle.x > rect.width) particle.vx *= -1;
    if (particle.y < 0 || particle.y > rect.height) particle.vy *= -1;
    
    const dot = document.createElement('div');
    dot.className = 'voice-particle';
    dot.style.left = particle.x + 'px';
    dot.style.top = particle.y + 'px';
    dot.style.opacity = particle.opacity.toString();
    dot.style.width = particle.size + 'px';
    dot.style.height = particle.size + 'px';
    
    container.appendChild(dot);
  });
  
  animationId = requestAnimationFrame(animateParticles);
};

onMounted(async () => {
  dbg('mounted route:', { id: route.params.id, roleId: routeRoleId });
  
  // 启动浮动粒子动画
  createFloatingParticles();
  animateFloatingParticles();
  
  // 优先从路由参数获取roleId并获取角色详情
  if (routeRoleId) {
    await fetchRoleDetail(routeRoleId);
    await startFromRole(routeRoleId);
  } else if (route.params.id) {
    // 如果有会话ID，直接获取历史记录（会在fetchHistory中获取角色信息）
    await fetchHistory(route.params.id as string);
  } else {
    conversation.value = null;
  }
  
  // 启动语音模式粒子动画
  createParticles();
  animateParticles();
});

onBeforeUnmount(() => {
  // 组件卸载时才真正中止SSE连接
  if (sseAbortController.value) {
    try { 
      sseAbortController.value.abort(); 
      dbg('组件卸载，中止SSE连接');
    } catch {}
  }
  if (voiceWs.value) {
    try { voiceWs.value.close(1000, '组件卸载'); } catch {}
    voiceWs.value = null;
  }
  if (isRecording.value) {
    try { stopRecording(); } catch {}
  }
  if (autoRestartTimer.value) {
    try { clearTimeout(autoRestartTimer.value); } catch {}
    autoRestartTimer.value = null;
  }
  countdownSeconds.value = 0;
  audioQueue.value.forEach(url => {
    try { URL.revokeObjectURL(url); } catch {}
  });
  audioQueue.value = [];
  isPlayingAudio.value = false;
  audioPermissionGranted.value = false;
  
  // 清理粒子动画
  if (animationId) {
    cancelAnimationFrame(animationId);
  }
  if (floatingAnimationId) {
    cancelAnimationFrame(floatingAnimationId);
  }
});
</script>

<style scoped>
.chat-container {
  height: calc(100vh - 64px); /* 减去顶部导航栏的高度 */
  min-height: 500px; /* 确保最小高度，避免在小屏幕上布局异常 */
  display: flex;
  flex-direction: column;
  background: radial-gradient(1200px 600px at 20% -10%, rgba(124,77,255,0.08), transparent), #0b0b0b;
  color: #fff;
  overflow: hidden;
  position: relative;
}

/* 背景装饰 */
.bg-decoration {
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 1;
}

.bg-circle {
  position: absolute;
  border-radius: 50%;
  background: linear-gradient(135deg, rgba(124,77,255,0.06), rgba(167,139,250,0.03));
  filter: blur(1px);
  animation: float 30s infinite ease-in-out;
}

.bg-circle-1 {
  width: 500px;
  height: 500px;
  top: -250px;
  right: -250px;
  animation-delay: 0s;
}

.bg-circle-2 {
  width: 300px;
  height: 300px;
  bottom: -150px;
  left: -150px;
  animation-delay: -10s;
}

.bg-circle-3 {
  width: 200px;
  height: 200px;
  top: 30%;
  right: 10%;
  animation-delay: -20s;
}

.bg-grid {
  position: absolute;
  inset: 0;
  background-image: 
    linear-gradient(rgba(124,77,255,0.02) 1px, transparent 1px),
    linear-gradient(90deg, rgba(124,77,255,0.02) 1px, transparent 1px);
  background-size: 80px 80px;
  animation: grid-move 50s linear infinite;
}

.bg-stars {
  position: absolute;
  inset: 0;
  background-image: 
    radial-gradient(2px 2px at 20px 30px, rgba(255,255,255,0.1), transparent),
    radial-gradient(2px 2px at 40px 70px, rgba(124,77,255,0.2), transparent),
    radial-gradient(1px 1px at 90px 40px, rgba(255,255,255,0.15), transparent),
    radial-gradient(1px 1px at 130px 80px, rgba(167,139,250,0.1), transparent);
  background-repeat: repeat;
  background-size: 150px 100px;
  animation: starsTwinkle 8s ease-in-out infinite;
}

@keyframes float {
  0%, 100% { transform: translateY(0px) rotate(0deg); }
  33% { transform: translateY(-40px) rotate(120deg); }
  66% { transform: translateY(20px) rotate(240deg); }
}

@keyframes grid-move {
  0% { transform: translate(0, 0); }
  100% { transform: translate(80px, 80px); }
}

@keyframes starsTwinkle {
  0%, 100% { opacity: 0.8; }
  50% { opacity: 1; }
}

/* 聊天头部 */
.chat-header {
  padding: 20px 24px;
  border-bottom: 1px solid rgba(124,77,255,0.15);
  background: rgba(20, 20, 20, 0.8);
  backdrop-filter: blur(20px);
  position: relative;
  z-index: 10;
  animation: slideDown 0.6s ease-out;
  flex-shrink: 0; /* 防止头部被压缩 */
}

.header-content {
  display: flex;
  align-items: center;
  max-width: 1200px;
  margin: 0 auto;
  width: 100%;
}

.avatar-container {
  position: relative;
  margin-right: 20px;
  flex-shrink: 0;
}

.avatar {
  border: 2px solid rgba(124,77,255,0.3);
  box-shadow: 0 8px 32px rgba(124,77,255,0.2);
  transition: all 0.3s ease;
}

.avatar:hover {
  transform: scale(1.05);
  box-shadow: 0 12px 40px rgba(124,77,255,0.3);
}

.avatar-glow {
  position: absolute;
  inset: -4px;
  background: linear-gradient(135deg, rgba(124,77,255,0.3), rgba(167,139,250,0.2));
  border-radius: 50%;
  filter: blur(8px);
  opacity: 0.6;
  z-index: -1;
  animation: avatarPulse 3s ease-in-out infinite;
}

.status-indicator {
  position: absolute;
  bottom: 4px;
  right: 4px;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: #666;
  border: 2px solid #0b0b0b;
  transition: all 0.3s ease;
}

.status-indicator.online {
  background: #4caf50;
  box-shadow: 0 0 12px rgba(76,175,80,0.6);
}

.header-info {
  flex: 1;
  min-width: 0;
}

.profile-name {
  margin: 0 0 8px;
  font-size: 28px;
  font-weight: 700;
  background: linear-gradient(90deg, #c7b8ff, #7c4dff 40%, #a78bfa);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.profile-meta {
  display: flex;
  align-items: center;
  color: #b5b5b5;
  font-size: 14px;
  gap: 8px;
  flex-wrap: wrap;
}

.dot {
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: #666;
  flex-shrink: 0;
}

.header-actions {
  display: flex;
  gap: 12px;
  flex-shrink: 0;
}

.action-btn {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 12px 20px;
  background: rgba(124,77,255,0.1);
  border: 1px solid rgba(124,77,255,0.3);
  border-radius: 12px;
  color: #7c4dff;
  font-size: 14px;
  font-weight: 500;
  cursor: pointer;
  transition: all 0.3s ease;
  white-space: nowrap;
}

.action-btn:hover {
  background: rgba(124,77,255,0.2);
  transform: translateY(-2px);
  box-shadow: 0 8px 25px rgba(124,77,255,0.2);
}

.follow-btn {
  background: linear-gradient(135deg, #7c4dff, #6a3cff);
  border-color: transparent;
  color: #fff;
}

.follow-btn:hover {
  background: linear-gradient(135deg, #6a3cff, #5a2cef);
  box-shadow: 0 8px 25px rgba(124,77,255,0.4);
}

/* 聊天主体 */
.chat-body {
  flex: 1;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  position: relative;
  z-index: 2;
  min-height: 0; /* 确保flex子元素可以正确收缩 */
}

/* 消息区域 */
.chat-messages {
  flex: 1;
  overflow-y: auto;
  padding: 24px;
  position: relative;
  scroll-behavior: smooth;
  min-height: 0; /* 确保可以正确收缩 */
}

.chat-messages::-webkit-scrollbar {
  width: 6px;
}

.chat-messages::-webkit-scrollbar-track {
  background: rgba(255,255,255,0.05);
  border-radius: 3px;
}

.chat-messages::-webkit-scrollbar-thumb {
  background: rgba(124,77,255,0.3);
  border-radius: 3px;
  transition: background 0.3s ease;
}

.chat-messages::-webkit-scrollbar-thumb:hover {
  background: rgba(124,77,255,0.5);
}

/* 加载状态 */
.loading-container {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 60px 0;
  animation: fadeIn 0.5s ease;
}

.loading-spinner {
  position: relative;
  width: 60px;
  height: 60px;
  margin-bottom: 20px;
}

.spinner-ring {
  position: absolute;
  border: 3px solid transparent;
  border-radius: 50%;
  animation: spin 2s linear infinite;
}

.spinner-ring:nth-child(1) {
  width: 60px;
  height: 60px;
  border-top-color: #7c4dff;
  animation-delay: 0s;
}

.spinner-ring:nth-child(2) {
  width: 45px;
  height: 45px;
  top: 7.5px;
  left: 7.5px;
  border-right-color: #a78bfa;
  animation-delay: -0.5s;
}

.spinner-ring:nth-child(3) {
  width: 30px;
  height: 30px;
  top: 15px;
  left: 15px;
  border-bottom-color: #c7b8ff;
  animation-delay: -1s;
}

.loading-text {
  color: #7c4dff;
  font-size: 16px;
  font-weight: 500;
  animation: pulse 2s ease-in-out infinite;
}

@keyframes spin {
  0% { transform: rotate(0deg); }
  100% { transform: rotate(360deg); }
}

@keyframes pulse {
  0%, 100% { opacity: 0.7; }
  50% { opacity: 1; }
}

/* 消息列表 */
.messages-list {
  display: flex;
  flex-direction: column;
  gap: 16px;
  max-width: 1200px;
  margin: 0 auto;
  width: 100%;
  padding-bottom: 20px; /* 减少底部padding，因为footer已有padding */
}

.message-wrapper {
  display: flex;
  animation: messageSlideIn 0.5s ease-out both;
  animation-delay: var(--delay);
  width: 100%;
}

.message-wrapper.sent {
  justify-content: flex-end;
}

.message-wrapper.received {
  justify-content: flex-start;
}

.message-container {
  display: flex;
  gap: 12px;
  max-width: 75%;
  min-width: 200px;
}

.message-wrapper.sent .message-container {
  flex-direction: row-reverse;
}

.message-avatar {
  width: 40px;
  height: 40px;
  border: 2px solid rgba(124,77,255,0.2);
  box-shadow: 0 4px 12px rgba(0,0,0,0.2);
  flex-shrink: 0;
  transition: all 0.3s ease;
}

.message-avatar:hover {
  transform: scale(1.1);
  box-shadow: 0 6px 16px rgba(124,77,255,0.3);
}

.message-content {
  display: flex;
  flex-direction: column;
  min-width: 0;
  flex: 1;
}

.message-bubble {
  padding: 16px 20px;
  border-radius: 20px;
  position: relative;
  backdrop-filter: blur(10px);
  border: 1px solid rgba(255,255,255,0.1);
  transition: all 0.3s ease;
  word-wrap: break-word;
  overflow-wrap: break-word;
}

.message-bubble:hover {
  transform: translateY(-1px);
  box-shadow: 0 8px 25px rgba(0,0,0,0.15);
}

.message-wrapper.received .message-bubble {
  background: rgba(30, 30, 30, 0.9);
  border-bottom-left-radius: 8px;
}

.message-wrapper.sent .message-bubble {
  background: linear-gradient(135deg, rgba(124,77,255,0.9), rgba(106,60,255,0.9));
  border-bottom-right-radius: 8px;
}

/* AI思考状态样式 */
.message-bubble.thinking {
  background: rgba(30, 30, 30, 0.9) !important;
  border: 1px solid rgba(124,77,255,0.3);
}

.thinking-indicator {
  display: flex;
  align-items: center;
  gap: 12px;
}

.thinking-icon {
  font-size: 20px;
  animation: thinkingRotate 2s ease-in-out infinite;
}

.thinking-text {
  color: #7c4dff;
  font-weight: 500;
  font-size: 14px;
}

.thinking-dots {
  display: flex;
  gap: 4px;
  align-items: center;
}

.thinking-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: rgba(124,77,255,0.8);
  animation: thinkingBounce 1.4s infinite ease-in-out;
}

.thinking-dot:nth-child(1) { animation-delay: 0s; }
.thinking-dot:nth-child(2) { animation-delay: 0.2s; }
.thinking-dot:nth-child(3) { animation-delay: 0.4s; }

@keyframes thinkingRotate {
  0%, 100% { transform: rotate(0deg); }
  50% { transform: rotate(10deg); }
}

@keyframes thinkingBounce {
  0%, 80%, 100% {
    transform: translateY(0);
    opacity: 0.5;
  }
  40% {
    transform: translateY(-6px);
    opacity: 1;
  }
}

.text-content {
  line-height: 1.6;
  font-size: 15px;
  text-align: left;
}

.text-content :deep(h1),
.text-content :deep(h2),
.text-content :deep(h3),
.text-content :deep(h4),
.text-content :deep(h5),
.text-content :deep(h6) {
  margin: 8px 0;
  font-weight: 600;
  text-align: left;
}

.text-content :deep(p) {
  margin: 8px 0;
  text-align: left;
}

.text-content :deep(ul),
.text-content :deep(ol) {
  margin: 8px 0 8px 20px;
  text-align: left;
}

.text-content :deep(li) {
  margin: 4px 0;
  text-align: left;
}

.text-content :deep(a) {
  color: #91caff;
  text-decoration: underline;
  transition: color 0.3s ease;
}

.text-content :deep(a:hover) {
  color: #7c4dff;
}

.text-content :deep(pre) {
  overflow: auto;
  background: rgba(0,0,0,0.3);
  padding: 12px;
  border-radius: 8px;
  border: 1px solid rgba(255,255,255,0.1);
  margin: 8px 0;
  text-align: left;
}

.text-content :deep(code) {
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
  background: rgba(255,255,255,0.1);
  padding: 2px 6px;
  border-radius: 4px;
  font-size: 14px;
}

.audio-content {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.audio-player {
  max-width: 250px;
  height: 40px;
  border-radius: 8px;
}

.audio-duration {
  font-size: 12px;
  opacity: 0.8;
  color: #b5b5b5;
}

.message-time {
  font-size: 12px;
  color: #888;
  margin-top: 6px;
  opacity: 0.8;
  transition: opacity 0.3s ease;
}

.message-wrapper.sent .message-time {
  text-align: right;
}

.message-wrapper.received .message-time {
  text-align: left;
}

.message-bubble:hover + .message-time {
  opacity: 1;
}

/* 输入区域 */
.chat-footer {
  background: linear-gradient(180deg, transparent 0%, rgba(11, 11, 11, 0.8) 20%, rgba(11, 11, 11, 0.95) 100%);
  flex-shrink: 0; /* 防止输入区域被压缩 */
  padding: 20px 24px; /* 增加内边距确保输入框有足够空间 */
  position: relative;
  z-index: 10;
}

.chat-input {
  /* background: rgba(25, 25, 25, 0.95); */
}

.chat-input :deep(.t-chat-sender__textarea) {
  /* background: transparent !important; */
  border: none !important;
  background: rgba(25, 25, 25, 0.95);
}

.input-container {
  max-width: 1200px;
  margin: 0 auto;
  position: relative;
  width: 100%;
  min-height: 60px; /* 确保输入区域有最小高度 */
}

.chat-input {
  width: 100%;
  border-radius: 16px;
  overflow: hidden;
  box-shadow: 0 8px 32px rgba(0,0,0,0.3);
  border: 1px solid rgba(124,77,255,0.2);
  transition: all 0.3s ease;
}

.chat-input:hover {
  border-color: rgba(124,77,255,0.4);
  box-shadow: 0 12px 40px rgba(124,77,255,0.1);
}

.chat-input :deep(.t-textarea__inner) {
  /* background: rgba(30, 30, 30, 0.9) !important; */
  backdrop-filter: blur(10px);
  border: none !important;
  color: #fff !important;
  padding: 16px 20px;
  font-size: 15px;
  line-height: 1.5;
}

.chat-input :deep(.t-textarea__inner::placeholder) {
  color: #888 !important;
}

.chat-input :deep(.t-chat-sender__inner) {
  background: rgba(30, 30, 30, 0.9) !important;
  backdrop-filter: blur(10px);
  border: none !important;
}

.chat-input :deep(.t-chat-sender) {
  background: rgba(30, 30, 30, 0.9) !important;
}

.input-action-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  border-radius: 12px;
  background: rgba(124,77,255,0.1);
  border: 1px solid rgba(124,77,255,0.2);
  color: #7c4dff;
  cursor: pointer;
  transition: all 0.3s ease;
  margin-left: 8px;
}

.input-action-btn:hover {
  background: rgba(124,77,255,0.2);
  transform: translateY(-2px);
  box-shadow: 0 6px 20px rgba(124,77,255,0.2);
}

.input-action-btn.recording {
  background: rgba(255,107,107,0.2);
  border-color: rgba(255,107,107,0.4);
  color: #ff6b6b;
  animation: recordingPulse 1.5s infinite;
}

/* 联网按钮特殊样式 */
.input-action-btn.web-search-btn {
  position: relative;
  overflow: hidden;
  transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
}

.input-action-btn.web-search-btn.active {
  background: linear-gradient(135deg, rgba(0, 150, 255, 0.3), rgba(124, 77, 255, 0.3));
  border-color: rgba(0, 150, 255, 0.6);
  color: #0096ff;
  box-shadow: 
    0 0 20px rgba(0, 150, 255, 0.4),
    0 0 40px rgba(0, 150, 255, 0.2),
    inset 0 1px 0 rgba(255, 255, 255, 0.2);
  transform: translateY(-1px);
}

.input-action-btn.web-search-btn.active .web-search-icon {
  animation: webSearchGlow 2s ease-in-out infinite alternate;
  filter: drop-shadow(0 0 8px rgba(0, 150, 255, 0.8));
}

.input-action-btn.web-search-btn .button-glow {
  position: absolute;
  inset: 0;
  background: linear-gradient(
    135deg, 
    rgba(0, 150, 255, 0.3) 0%, 
    rgba(124, 77, 255, 0.2) 50%, 
    rgba(0, 150, 255, 0.3) 100%
  );
  opacity: 0;
  transition: opacity 0.4s ease;
  border-radius: 12px;
  animation: buttonGlowPulse 3s ease-in-out infinite;
}

.input-action-btn.web-search-btn.active .button-glow {
  opacity: 1;
}

.web-search-icon {
  position: relative;
  z-index: 2;
  font-size: 18px;
  transition: all 0.3s ease;
}

@keyframes webSearchGlow {
  0% {
    filter: drop-shadow(0 0 8px rgba(0, 150, 255, 0.8));
    transform: scale(1);
  }
  100% {
    filter: drop-shadow(0 0 12px rgba(0, 150, 255, 1));
    transform: scale(1.05);
  }
}

@keyframes buttonGlowPulse {
  0%, 100% {
    background: linear-gradient(
      135deg, 
      rgba(0, 150, 255, 0.3) 0%, 
      rgba(124, 77, 255, 0.2) 50%, 
      rgba(0, 150, 255, 0.3) 100%
    );
  }
  50% {
    background: linear-gradient(
      135deg, 
      rgba(124, 77, 255, 0.3) 0%, 
      rgba(0, 150, 255, 0.3) 50%, 
      rgba(124, 77, 255, 0.3) 100%
    );
  }
}

@keyframes recordingPulse {
  0%, 100% {
    transform: scale(1);
    box-shadow: 0 0 0 0 rgba(255,107,107,0.7);
  }
  50% {
    transform: scale(1.05);
    box-shadow: 0 0 0 8px rgba(255,107,107,0);
  }
}

/* 语音对话浮层 */
.voice-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,0.85);
  backdrop-filter: blur(15px);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1000;
  animation: overlayFadeIn 0.4s ease;
}

.voice-panel {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 32px;
  position: relative;
  z-index: 2;
}

.voice-circle-container {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
}

.voice-circle {
  width: 240px;
  height: 240px;
  border-radius: 50%;
  background: linear-gradient(135deg, #7c4dff 0%, #a78bfa 50%, #c7b8ff 100%);
  box-shadow: 
    0 20px 60px rgba(124,77,255,0.3),
    inset 0 0 40px rgba(255,255,255,0.1);
  transition: all 0.4s ease;
  position: relative;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: center;
}

.voice-inner-circle {
  width: 200px;
  height: 200px;
  border-radius: 50%;
  background: linear-gradient(135deg, rgba(124,77,255,0.2), rgba(167,139,250,0.1));
  backdrop-filter: blur(10px);
  border: 1px solid rgba(255,255,255,0.2);
}

.voice-circle.voice-animating {
  animation: voicePulse 2s ease-in-out infinite;
  box-shadow: 
    0 25px 80px rgba(124,77,255,0.4),
    inset 0 0 40px rgba(255,255,255,0.15);
}

.voice-ripple {
  position: absolute;
  border-radius: 50%;
  border: 2px solid rgba(124,77,255,0.3);
  animation: rippleExpand 2s ease-out infinite;
  z-index: 1;
}

.voice-ripple-1 {
  width: 240px;
  height: 240px;
  animation-delay: 0s;
}

.voice-ripple-2 {
  width: 240px;
  height: 240px;
  animation-delay: 0.7s;
}

.voice-ripple-3 {
  width: 240px;
  height: 240px;
  animation-delay: 1.4s;
}

.voice-status {
  color: rgba(255, 255, 255, 0.9);
  font-size: 18px;
  text-align: center;
  min-height: 48px;
  display: flex;
  align-items: center;
  justify-content: center;
  margin-top: 16px;
  line-height: 1.5;
}

.recording-hint {
  color: #fff !important;
  font-size: 20px !important;
  font-weight: 600;
  animation: recordingBlink 2s infinite;
  text-shadow: 0 2px 8px rgba(0,0,0,0.3);
}

.voice-debug {
  position: absolute;
  bottom: -80px;
  left: 50%;
  transform: translateX(-50%);
}

.debug-btn {
  background: rgba(255,255,255,0.1);
  border: 1px solid rgba(255,255,255,0.2);
  border-radius: 8px;
  color: #fff;
  padding: 8px 16px;
  font-size: 12px;
  cursor: pointer;
  transition: all 0.3s ease;
  opacity: 0.7;
}

.debug-btn:hover {
  background: rgba(255,255,255,0.2);
  opacity: 1;
}

.voice-actions {
  display: flex;
  gap: 24px;
}

.voice-action-btn {
  width: 64px;
  height: 64px;
  border-radius: 50%;
  border: 2px solid rgba(255,255,255,0.2);
  background: rgba(255,255,255,0.1);
  backdrop-filter: blur(10px);
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: all 0.3s ease;
  font-size: 18px;
}

.voice-action-btn:hover {
  background: rgba(255,255,255,0.2);
  transform: translateY(-2px);
  box-shadow: 0 8px 25px rgba(0,0,0,0.2);
}

.voice-action-btn.primary {
  background: rgba(124,77,255,0.2);
  border-color: rgba(124,77,255,0.5);
  color: #7c4dff;
}

.voice-action-btn.primary:hover {
  background: rgba(124,77,255,0.3);
  box-shadow: 0 8px 25px rgba(124,77,255,0.3);
}

.voice-action-btn.danger {
  background: rgba(255,107,107,0.2);
  border-color: rgba(255,107,107,0.5);
  color: #ff6b6b;
}

.voice-action-btn.danger:hover {
  background: rgba(255,107,107,0.3);
  box-shadow: 0 8px 25px rgba(255,107,107,0.3);
}

.voice-action-btn.recording-pulse {
  animation: voiceRecordingPulse 1.5s infinite;
}

.recording-instruction {
  position: absolute;
  bottom: -120px;
  left: 50%;
  transform: translateX(-50%);
  background: rgba(255,107,107,0.9);
  backdrop-filter: blur(10px);
  color: white;
  padding: 12px 24px;
  border-radius: 16px;
  font-size: 14px;
  font-weight: 500;
  white-space: nowrap;
  box-shadow: 0 8px 25px rgba(255,107,107,0.3);
  border: 1px solid rgba(255,255,255,0.2);
}

.instruction-content {
  display: flex;
  align-items: center;
  gap: 8px;
}

/* 浮动粒子效果 */
.floating-particles {
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 1;
}

.floating-particle {
  position: absolute;
  background: rgba(124,77,255,0.6);
  border-radius: 50%;
  pointer-events: none;
}

/* 语音模式粒子 */
.particles-container {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 1;
}

.voice-particle {
  position: absolute;
  background: rgba(124,77,255,0.8);
  border-radius: 50%;
  pointer-events: none;
}

/* 动画定义 */
@keyframes slideDown {
  from {
    opacity: 0;
    transform: translateY(-20px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

@keyframes messageSlideIn {
  from {
    opacity: 0;
    transform: translateY(20px) scale(0.95);
  }
  to {
    opacity: 1;
    transform: translateY(0) scale(1);
  }
}

@keyframes fadeIn {
  from { opacity: 0; }
  to { opacity: 1; }
}

@keyframes avatarPulse {
  0%, 100% {
    opacity: 0.6;
    transform: scale(1);
  }
  50% {
    opacity: 0.8;
    transform: scale(1.05);
  }
}

@keyframes overlayFadeIn {
  from {
    opacity: 0;
    backdrop-filter: blur(0px);
  }
  to {
    opacity: 1;
    backdrop-filter: blur(15px);
  }
}

@keyframes voicePulse {
  0%, 100% {
    transform: scale(1);
    box-shadow: 
      0 20px 60px rgba(124,77,255,0.3),
      inset 0 0 40px rgba(255,255,255,0.1);
  }
  50% {
    transform: scale(1.05);
    box-shadow: 
      0 25px 80px rgba(124,77,255,0.5),
      inset 0 0 40px rgba(255,255,255,0.2);
  }
}

@keyframes rippleExpand {
  0% {
    transform: scale(0.8);
    opacity: 1;
  }
  100% {
    transform: scale(2);
    opacity: 0;
  }
}

@keyframes recordingBlink {
  0%, 50% { opacity: 1; }
  51%, 100% { opacity: 0.7; }
}

@keyframes voiceRecordingPulse {
  0%, 100% {
    transform: scale(1);
    box-shadow: 0 0 0 0 rgba(124,77,255,0.7);
  }
  50% {
    transform: scale(1.1);
    box-shadow: 0 0 0 15px rgba(124,77,255,0);
  }
}

/* 过渡动画 */
.message-enter-active,
.message-leave-active {
  transition: all 0.4s ease;
}

.message-enter-from {
  opacity: 0;
  transform: translateY(20px) scale(0.9);
}

.message-leave-to {
  opacity: 0;
  transform: translateY(-20px) scale(0.9);
}

.voice-overlay-enter-active,
.voice-overlay-leave-active {
  transition: all 0.4s ease;
}

.voice-overlay-enter-from,
.voice-overlay-leave-to {
  opacity: 0;
  backdrop-filter: blur(0px);
}

.status-fade-enter-active,
.status-fade-leave-active {
  transition: all 0.3s ease;
}

.status-fade-enter-from,
.status-fade-leave-to {
  opacity: 0;
  transform: translateY(10px);
}

.instruction-slide-enter-active,
.instruction-slide-leave-active {
  transition: all 0.3s ease;
}

.instruction-slide-enter-from,
.instruction-slide-leave-to {
  opacity: 0;
  transform: translateX(-50%) translateY(20px);
}

.button-scale-enter-active,
.button-scale-leave-active {
  transition: all 0.2s ease;
}

.button-scale-enter-from,
.button-scale-leave-to {
  opacity: 0;
  transform: scale(0.8);
}

.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.3s ease;
}

.fade-enter-from,
.fade-leave-to {
  opacity: 0;
}

/* 响应式设计 */
@media (max-width: 768px) {
  .chat-header {
    padding: 16px 20px;
  }
  
  .profile-name {
    font-size: 24px;
  }
  
  .chat-messages {
    padding: 16px 20px;
  }
  
  .message-container {
    max-width: 85%;
  }
  
  .chat-footer {
    padding: 16px 20px 24px;
  }
  
  .voice-circle {
    width: 200px;
    height: 200px;
  }
  
  .voice-inner-circle {
    width: 160px;
    height: 160px;
  }
  
  .voice-ripple-1,
  .voice-ripple-2,
  .voice-ripple-3 {
    width: 200px;
    height: 200px;
  }
  
  .voice-status {
    font-size: 16px;
  }
  
  .recording-hint {
    font-size: 18px !important;
  }
}

@media (max-width: 480px) {
  .header-content {
    flex-direction: column;
    align-items: center;
    gap: 12px;
  }
  
  .avatar-container {
    margin-right: 0;
  }
  
  .header-info {
    text-align: center;
  }
  
  .message-container {
    max-width: 95%;
  }
  
  .voice-circle {
    width: 160px;
    height: 160px;
  }
  
  .voice-inner-circle {
    width: 120px;
    height: 120px;
  }
  
  .voice-action-btn {
    width: 56px;
    height: 56px;
  }
}
</style>